package com.gitee.chili.leisure.core;

import com.gitee.chili.leisure.ResponseCode;
import com.gitee.chili.leisure.ResponseResult;
import com.gitee.chili.leisure.exception.BusinessException;
import com.gitee.chili.leisure.model.MessageCmdEnum;
import com.gitee.chili.leisure.util.AuxiliaryChannel;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.function.Function;

/**
 * 基础消息模型
 *
 * @author week solider
 * @since 2020.08.31
 */
public abstract class AbstractDefaultMessage implements IMessage {

    private static final Logger logger = LoggerFactory.getLogger(AbstractDefaultMessage.class);

    /**
     * 接收到的数据包 || 响应的数据包
     * 服务端接收到消息时, 此时充当接收数据角色.
     * 服务端响应消息时, 此时是响应的数据
     */
    private Packet packet;

    /**
     * 当前的消息信道
     */
    private transient Channel channel;


    public AbstractDefaultMessage() {
    }

    public AbstractDefaultMessage(Packet packet, Channel channel) {
        this.packet = packet;
        this.channel = channel;
    }

    @Override
    public void decoder() throws InvalidProtocolBufferException {
        decoder(packet.getBody());
    }

    /**
     * 对 proto 数据进行解码, 解码后是一个可用的 java 对象
     *
     * @param body 二进制数据
     * @throws InvalidProtocolBufferException 无效的 proto 二进制数据
     */
    protected abstract void decoder(byte[] body) throws InvalidProtocolBufferException;


    /**
     * 对消息进行编码, 编码后得到新的数据包, 此时接受的的数据包将替换为响应的数据包
     */
    public abstract Packet parseBody();

    /**
     * 数据包发送至客户端
     */
    @Override
    public void writeFlush() {
        encoder();
        if (packet == null) {
            logger.error("服务端写回数据为空");
            return;
        }
        channel.writeAndFlush(packet);
    }

    @Override
    public void encoder() {
        packet = parseBody();
    }

    @Override
    public Packet getPacket() {
        return packet;
    }

    /**
     * 获取消息信道
     */
    @Override
    public Channel getChannel() {
        return channel;
    }


    // ------------------------------------------------------------------------------  辅助子类


    /**
     * 断言 RPC 请求失败结果处理
     */
    protected <T> void asserts(ResponseResult<T> result) {
        boolean isFail = !(Objects.isNull(result) || !result.getCode().equals(ResponseCode.SUCCESS.getCode()));

        if (isFail) {
            // 告知客户端异常信息
            AuxiliaryChannel.errorMessage(getChannel(), packet.getCmd(), result.getCode(), result.getMessage());

            logger.error("命令 {} 请求失败 , code: {}  ,  message: {}", packet.getCmd(), result.getCode(), result.getMessage());

            // 一定要抛出异常, 负责消息还在进行传递
            throw new BusinessException(result.getMessage());
        }
    }

    /**
     * 断言结果是否为真
     */
    protected <R> void asserts(boolean flag, Function<MessageCmdEnum.Command, R> function) {
        if (flag) {
            R r = function.apply(packet.getCmd());

            if (r instanceof String) {
                // 一定要抛出异常, 负责消息还在进行传递
                throw new BusinessException(r.toString());
            } else {
                // 一定要抛出异常, 负责消息还在进行传递
                throw new BusinessException((ResponseCode) r);
            }
        }
    }

    /**
     * 获取请求的命令
     */
    public MessageCmdEnum.Command getCommand(){
        return packet.getCmd();
    }

    /**
     * 获取玩家ID
     */
    public Long getMemberId() {
        try {
            return Long.parseLong(channel.attr(AttributeKey.valueOf("userId")).get().toString());
        } catch (NumberFormatException e) {
            logger.error("获取玩家ID失败! , cmd: {}", packet.getCmd());
            AuxiliaryChannel.errorMessage(channel, packet.getCmd(), ResponseCode.FAIL);
            throw new BusinessException("请求失败");
        }
    }

    /**
     * 获取房间号
     */
    public Long getRoomNum() {
        try {
            return Long.parseLong(channel.attr(AttributeKey.valueOf("roomNum")).get().toString());
        } catch (NumberFormatException e) {
            logger.error("获取房间号失败! , cmd: {}", packet.getCmd());
            AuxiliaryChannel.errorMessage(channel, packet.getCmd(), ResponseCode.FAIL);
            throw new BusinessException("请求失败");
        }
    }

}
